/*
 * JSON001BlipClient.java
 *
 * Created on 10 październik 2007, 14:46
 *
 */

package kosciak.blip4j.api.json001;

import java.io.FileNotFoundException;
import kosciak.blip4j.BlipClient;
import kosciak.blip4j.BlipError;
import kosciak.blip4j.BlipException;
import kosciak.blip4j.Update;
import kosciak.blip4j.User;
import kosciak.blip4j.utils.http.HTTPClient;
import kosciak.blip4j.utils.http.SimpleHTTPClient;
import kosciak.blip4j.utils.http.HTTPResponse;

import java.io.IOException;

import java.net.URL;

import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import org.apache.commons.codec.EncoderException;

/**
 * Implementation of BlipClient for Blip.pl API version 0.01 using JSON
 * @author Wojtek 'KosciaK' Pietrzok
 * @since 0.1
 */
public class JSON001BlipClient extends BlipClient{
    
    private String blipAPI = "api.blip.pl";
    
    private HTTPClient client;
    
    private final String UPDATES = "/updates";
    private final String USERS = "/users";
    private final String FRIENDS = "/friends";
    private final String POLL = "/updates/poll";    // samo "updates" pobiera _wysłane_ przez _mnie_
    private final String BLIPOSPHERE = "/bliposphere";
    private final String SHORTLINKS = "/shortlinks";
    private final String STATUSES = "/statuses";
    private final String SUBSCRIPTIONS = "/subscriptions";
    
    private final String API_VERSION = "Blip API version 0.1 / JSON";
    
    private final int MAX_LENGTH = 160;
    
    
    /** Creates a new instance of JSON001BlipClient */
    private JSON001BlipClient(String username, String password) throws BlipException {
        super(username, password);
        setupClient();
        setHeaders(); 
        checkAuthorization();
    }
    
    /**
     * Gets an instance of BlipClient that uses implementation for Blip.pl API version 0.01 using JSON.
     * @param username a username of an User
     * @param password a password of an User
     * @return implementation of BlipClient
     * @throws kosciak.blip4j.BlipException if an error occurs
     */
    public static BlipClient getInstance(String username, String password) throws BlipException {
        return new JSON001BlipClient(username, password);
    }

    @Override
    public String getAPIVersion() {
        return API_VERSION;
    }

    @Override
    public int setStatus(String body) throws BlipException {
        return sendUpdate(body);
    }
    
    @Override
    public int setStatus(String body, boolean allowLonger) throws BlipException {
        if (!allowLonger)
            return sendUpdate(body);
        
        List<String> parts = divideBody(body, MAX_LENGTH);
        int last = 0;
        for (String part : parts) {
            last = sendUpdate(part);
        }
        return last;
    }

    @Override
    public int sendDirectMessage(User user, String body) throws BlipException {
        return sendUpdate(">" + user.getLogin() + ": " + body);
    }

    @Override
    public int sendDirectMessage(User user, String body, boolean allowLonger) throws BlipException {
        if (!allowLonger)
            return sendUpdate(">" + user.getLogin() + ": " + body);
        
        List<String> parts = divideBody(body, MAX_LENGTH);
        int last = 0;
        for (String part : parts) {
            last = sendUpdate(">" + user.getLogin() + ": " + part);
        }
        return last;
    }

    @Override
    public List<User> getFriends() throws BlipException {
        return doGetFriends(FRIENDS);
    }
    
    @Override
    public List<User> getUserFriends(User user) throws BlipException {
        return doGetFriends(USERS + "/" + user.getLogin() + SUBSCRIPTIONS);
    }

    @Override
    public User getUser(int userId) throws BlipException {
        User user = doGetUser("" + userId);
        
        if (user.getId() != userId)
            throw new BlipException(BlipError.NoSuchUser);
            
        return user;
    } 

    @Override
    public User getUser(String userLogin) throws BlipException {
        User user = doGetUser(userLogin);
        
        if ( ! userLogin.equalsIgnoreCase(user.getLogin()))
            throw new BlipException(BlipError.NoSuchUser);
            
        return user;
    } 
    
    @Override
    public Update getCurrentStatus(User user) throws BlipException {
        String response = doGET(USERS + "/" + user.getLogin());
        Update update = null;
        try {
            JSONObject obj = new JSONObject(response);
            JSONObject statusObject = obj.getJSONObject("current_status");
            update = JSON001Status.getInstance(statusObject, user);
        } catch (JSONException ex) {
            throw new BlipException(BlipError.CantParseServerAnswer);
        }
        return update;
   }

    @Override
    public Update getUpdate(int updateId) throws BlipException {
        String response = doGET(UPDATES + "/" + updateId);

        Update update = null;
        try {
            JSONObject obj = new JSONObject(response);
            update = JSON001Update.getInstance(obj);            
        } catch (JSONException ex) {
            throw new BlipException(BlipError.CantParseServerAnswer);
        }
        return update;
        
    }

    @Override
    public List<Update> poll() throws BlipException {
        return doPoll(POLL);
    }

    @Override
    public List<Update> poll(int updateId) throws BlipException {
        return doPoll(POLL + "/" + updateId);
    }

    @Override
    public List<Update> pollStatuses() throws BlipException {
        return pollStatuses(0);
    }

    @Override
    public List<Update> pollStatuses(int updateId) throws BlipException {
        return doPoll(STATUSES, updateId);
    }

    @Override
    public List<Update> pollBliposphere() throws BlipException {
        return pollBliposphere(0);
    }

    @Override
    public List<Update> pollBliposphere(int updateId) throws BlipException {
        return doPoll(BLIPOSPHERE, updateId);
    }

    @Override
    public List<Update> pollUser(User user) throws BlipException {
        return pollUser(user, 0);
    }

    @Override
    public List<Update> pollUser(User user, int updateId) throws BlipException {
        return doPoll(USERS + "/" + user.getLogin() + UPDATES, updateId);
    }

    @Override
    public List<Update> pollUserStatuses(User user) throws BlipException {
        return pollUserStatuses(user, 0);
    }

    @Override
    public List<Update> pollUserStatuses(User user, int updateId) throws BlipException {
        return doPoll(USERS + "/" + user.getLogin() + STATUSES, updateId);
    }
    
    @Override
    public URL expandURL(String url) throws BlipException{
        String file = url.substring(url.lastIndexOf("/"));
        HTTPResponse response;
        try {
            response = client.doGET(SHORTLINKS + file);
            response.getStatusCode();
        } catch (IOException ex) {
            throw new BlipException(BlipError.CantExpandURL);
        }
        return response.getURL();
    }

    @Override
    public void deleteUpdate(int updateId) throws UnsupportedOperationException, BlipException {
        throw new UnsupportedOperationException("Not implemented in " + getAPIVersion());
    }

    @Override
    public void addFriend(User user) throws UnsupportedOperationException, BlipException {
        throw new UnsupportedOperationException("Not implemented in " + getAPIVersion());
    }

    @Override
    public void removeFriend(User user) throws UnsupportedOperationException, BlipException {
        throw new UnsupportedOperationException("Not implemented in " + getAPIVersion());
    }

    /*
     *  Setup HttpClient
     */
    
    private void setupClient() throws BlipException {
        client = new SimpleHTTPClient(blipAPI);
        try {
            client.setBasicAuthentication(getUsername(), getPassword());
        } catch (EncoderException ex) {
            throw new BlipException(BlipError.AuthorizationError);
        }
                
    }

    private void setHeaders() {
        client.addRequestHeader("User-Agent", getUserAgent());
        client.addRequestHeader("Host", "api.blip.pl");
        client.addRequestHeader("Pragma", "no-cache");
        client.addRequestHeader("X-blip-api", "0.01");
        client.addRequestHeader("Accept","application/json");
        client.addRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
    }

    
    /*
     *  Do POST and GET requests
     */
    
    private String doPOST(String content) throws BlipException {
        String confirmation;
        try {
            HTTPResponse response = client.doPOST(UPDATES, content);
            confirmation = response.getContentAsString();
        } catch (IOException ex) {
            throw new BlipException(BlipError.ServerError);
        }
        return confirmation;
    }
    
    private String doGET(String file) throws BlipException {
        String json;
        try {
            HTTPResponse response = client.doGET(file);
            json = response.getContentAsString();
        } catch (FileNotFoundException ex) {
            if (file.startsWith(UPDATES))
                throw new BlipException(BlipError.NoSuchUpdate);
            throw new BlipException(BlipError.ServerError);
        } catch (IOException ex) {
            throw new BlipException(BlipError.ServerError);
        }
        return json;
    }
    
    /*
     *  Poll List of Updates
     */

    private List<Update> doPoll(String file) throws BlipException {
        List<Update> updates = new ArrayList<Update>();
        try {
            JSONArray updatesArray = new JSONArray(doGET(file));
            for (int i = 0 ; i < updatesArray.length() ; i++) {
                JSONObject updateObject = updatesArray.getJSONObject(i);
                Update update = JSON001Update.getInstance(updateObject);
                updates.add(update);
            }
        } catch (JSONException ex) {
            throw new BlipException(BlipError.CantParseServerAnswer);
        }
        Collections.reverse(updates);
        return updates;
    }

    private List<Update> doPoll(String file, int updateId) throws BlipException {
        List<Update> updates = doPoll(file);
        int size = updates.size();
        int first = updates.get(0).getId();
        if (updateId != 0) {
            int i;
            for(i = 0; i < size ; i++){
                if(updates.get(i).getId() > updateId)
                    break;
            }
            
            return updates.subList(i, size);    
        }
        return updates;
    }
    
    /*
     *  get User with specified Login or ID
     */
    
    private User doGetUser(String loginOrId) throws BlipException {
        String json = doGET(USERS + "/" + loginOrId);
        User user = null;
        
        try {
            JSONObject obj = new JSONObject(json);
            user = JSON001User.getInstance(obj);
        } catch (JSONException ex) {
            throw new BlipException(BlipError.CantParseServerAnswer);
        }
                     
        return user;
    }
    
    
    /*
     *  Get friends list
     */
    
    private List<User> doGetFriends(String file) throws BlipException {
        JSONArray friendsArray;
        List<User> users = new ArrayList<User>();
        try {
            friendsArray = new JSONArray(doGET(file));
            for (int i = 0 ; i < friendsArray.length() ; i++) {
                User user = JSON001User.getInstance(friendsArray.getJSONObject(i));
                users.add(user);
            }
        } catch (JSONException ex) {
            throw new BlipException(BlipError.CantParseServerAnswer);
        }
        return users;
    }

    /*
     *  Parse ID of the Update from confirmation message
     */
    
    private int getLastID(String confirmation) throws BlipException{
        int lastID;
        try {
            lastID = Integer.parseInt(confirmation.substring(confirmation.lastIndexOf("/") + 1).trim());
        } catch (NumberFormatException ex) {
            throw new BlipException(BlipError.CantParseServerAnswer);
        }
        
        return lastID;
    }
    
    /*
     *  Send Update
     */
    
    private int sendUpdate(String body) throws BlipException {
        int size = body.length();
        if (body.startsWith(">")) 
            size = size - body.substring(0, body.indexOf(':')).length();
        
        if (size > MAX_LENGTH)
            throw new BlipException(BlipError.MessageTooLong);
        
        String response = doPOST("body=" + body);
        int lastID = getLastID(response);
        
        return lastID;
    }
    
}
